<!doctype html>

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>css3 transform 实现元素的拖动</title>
  </head>

  <style>
    html,
    body {
      padding: 0;
      margin: 0;
      height: 100%;
    }

    .container {
      position: relative;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 600px;
      height: 600px;
      box-shadow: 0 0 8px 0 #353535;
    }

    .drop_box {
      width: 80px;
      height: 80px;
      border-radius: 100%;
      background-color: skyblue;
      cursor: move;
      transform: translate3d(0px, 0px, 0px);
    }
  </style>

  <body>
    <div class="container">
      <div class="drop_box"></div>
    </div>

    <script>
      const $ = (selector) => {
        return document.querySelector(selector);
      };
      const container = $(".container");
      const drop_box = $(".drop_box");

      start(container, drop_box, ({ x, y }) => {
        console.log(x, y);
      });

      function start(container, drag_el, callback) {
        const getTargetElement = (el) => {
          return el.getBoundingClientRect();
        };

        const getMousePos = (el, ev) => {
          const target = getTargetElement(el);
          return {
            x: ev.clientX - target.x,
            y: ev.clientY - target.y,
          };
        };

        const b = getTargetElement(drag_el);
        const c = getTargetElement(container);
        // 可拖动的范围
        const _h = c.height - b.height;
        const _w = c.width - b.width;

        drag_el.x = 0;
        drag_el.y = 0;
        drag_el.velocityX = 0;
        drag_el.velocityY = 0;
        drag_el.isDragging = false;
        drag_el.isThrown = false;
        drag_el.lastTime = 0;
        drag_el.lastX = 0;
        drag_el.lastY = 0;

        // 物理系数
        drag_el.gravity = 0.5;
        drag_el.airResistance = 0.99;
        drag_el.damping = 0.7;

        // 投掷物初始位置
        const initialPosition = {
          x: 0,
          y: 0,
        };

        const currentTransform = { x: 0, y: 0 };

        const updatePos = (el, x, y) => {
          // el.style.transform = `translate3d(${x}px, ${y}px, 0px)`;
          currentTransform.x = x;
          currentTransform.y = y;
          el.style.cssText = `transform: translate3d(${x}px, ${y}px, 0px)`;
        };
        // 设置初始位置
        updatePos(drag_el, initialPosition.x, initialPosition.y);

        // 鼠标/触摸事件处理
        drag_el.addEventListener("mousedown", startDrag);
        container.addEventListener("mousemove", drag);
        container.addEventListener("mouseup", endDrag);
        container.addEventListener("mouseleave", endDrag);

        // 鼠标按下开启优化, 注意是给容器开启 el.style.setProperty('will-change', 'transform');"
        // 如果是拖动抬起时就取消优化,
        // 如果是拖动投掷出去,等停下来,再取消优化 el.style.setProperty('will-change', 'auto');

        function startDrag(e) {
          e.preventDefault();

          drag_el.isDragging = true;
          drag_el.isThrown = false;

          if (drag_el.animationId) {
            cancelAnimationFrame(drag_el.animationId);
            drag_el.animationId = null;
          }
          drag_el.lastX = e.clientX - drag_el.offsetLeft;
          drag_el.lastY = e.clientY - drag_el.offsetTop;
          drag_el.lastTime = Date.now();
        }
        function drag(e) {
          e.preventDefault();
          if (!drag_el.isDragging) return;
          const clientX = e.clientX;
          const clientY = e.clientY;

          // 计算新位置
          const deltaX = clientX - drag_el.lastX;
          const deltaY = clientY - drag_el.lastY;

          let newX = currentTransform.x + deltaX;
          let newY = currentTransform.y + deltaY;

          // 限制在场景内
          newX = Math.max(
            0,
            Math.min(newX, container.offsetWidth - drag_el.offsetWidth),
          );
          newY = Math.max(
            0,
            Math.min(newY, container.offsetHeight - drag_el.offsetHeight),
          );

          updatePos(drag_el, newX, newY);

          // 计算投掷瞬间的速度
          const currentTime = Date.now();
          const deltaTime = currentTime - drag_el.lastTime;

          if (deltaTime > 0) {
            // drag_el.velocityX = velocityX;
            // drag_el.velocityY = velocityY;

            // x 轴方向的速度
            const velocityX = (deltaX / deltaTime) * 100;
            // y 轴方向的速度
            const velocityY = (deltaY / deltaTime) * 100;
            // 合速度
            const speed = Math.sqrt(velocityX ** 2 + velocityY ** 2);
            // 计算角度(速度方向)
            const angle = Math.atan2(velocityY, velocityX);

            drag_el.velocityX = speed * Math.cos(angle);
            drag_el.velocityY = speed * Math.sin(angle);
          }

          drag_el.lastX = clientX;
          drag_el.lastY = clientY;
        }
        function endDrag(e) {
          if (!drag_el.isDragging) return;
          drag_el.isDragging = false;
          drag_el.isThrown = true;

          animate();
        }

        function animate() {
          if (!drag_el.isThrown) return;
          console.log("x");

          // 正负号控制方向
          // drag_el.velocityY -= drag_el.gravity; // 正负号表示向上或向下掉落
          drag_el.velocityY += drag_el.gravity; // 正负号表示向上或向下掉落

          // 空气阻力控制减速
          drag_el.velocityX *= drag_el.airResistance;
          drag_el.velocityY *= drag_el.airResistance;

          // 计算新位置
          let newX = currentTransform.x + drag_el.velocityX;
          let newY = currentTransform.y + drag_el.velocityY;

          // 边界碰撞检测
          let hitBoundary = false;

          // _w = container.offsetWidth - drag_el.offsetWidth
          if (newX <= 0 || newX >= _w) {
            drag_el.velocityX = -drag_el.velocityX * drag_el.damping;
            newX = newX <= 0 ? 0 : _w;
            hitBoundary = true;
          }

          // _h = container.offsetHeight - drag_el.offsetHeight
          if (newY <= 0 || newY >= _h) {
            drag_el.velocityY = -drag_el.velocityY * drag_el.damping;
            newY = newY <= 0 ? 0 : _h;
            hitBoundary = true;

            // 停止条件
            if (
              Math.abs(drag_el.velocityY) < 0.3 &&
              Math.abs(drag_el.velocityX) < 0.3
            ) {
              drag_el.isThrown = false;
              drag_el.velocityX = 0;
              drag_el.velocityY = 0;

              console.log("停止");
              callback &&
                callback({
                  x: newX,
                  y: newY,
                });
              return;
            }
          }

          updatePos(drag_el, newX, newY);

          drag_el.animationId = requestAnimationFrame(animate);
        }
      }
    </script>
  </body>
</html>
